/* weehttp
* Copyright (c) 2008 Jeffery Myers
*
* This package is free software;  you can redistribute it and/or
* modify it under the terms of the license found in the file
* named COPYING that should have accompanied this file.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

#include "templates.h"
#include "textUtils.h"
#include "fileUtils.h"
#include "timeUtils.h"

#include <stdio.h>
#include <stdarg.h>
#include <sstream>
#include <algorithm>

bool TemplateMetaData::exists ( const char* key )
{
  if (!key)
    return false;

  std::string keyName = makeupper(key);

  return data.find(keyName) != data.end();
}

std::vector<std::string> TemplateMetaData::get ( const char* key )
{
  std::vector<std::string> vals;

  if (key)
  {
    std::string keyName = makeupper(key);

    if ( data.find(keyName) != data.end())
      vals = data[keyName];
  }

  return vals;
}

std::string TemplateMetaData::getFirst ( const char* key )
{
  std::string val;

  if (key)
  {
    std::string keyName = makeupper(key);

    if ( data.find(keyName) != data.end())
    {
      if (data[keyName].size())
	val = data[keyName][0];
    }
  }
  return val;
}

void TemplateMetaData::add ( const char* key, const char* val )
{
  if (!key || !val)
    return;

  std::string keyName = makeupper(key);

  if (!exists(key))
  {
    std::vector<std::string> t;
    data[keyName] = t;
  }
  data[keyName].push_back(std::string(val));
}

Templateiser::Templateiser( Templateiser *t )
{
  parent = t;
  startTimer();
  setDefaultTokens();
}

Templateiser::~Templateiser()
{

}

void Templateiser::startTimer ( void )
{
  startTime = getTimerSeconds();
}

void Templateiser::addKey ( const char *key, TemplateKeyCallback callback )
{
  if (!key || !callback)
    return;

  std::string k;
  tolower(key,k);

  keyFuncCallbacks[k] = callback;
  ClassMap::iterator itr = keyClassCallbacks.find(k);
  if (itr != keyClassCallbacks.end())
    keyClassCallbacks.erase(itr);
}

void Templateiser::addKey ( const char *key, TemplateCallbackClass *callback )
{
  if (!key || !callback)
    return;

  std::string k;
  tolower(key,k);

  keyClassCallbacks[k] = callback;
  KeyMap::iterator itr = keyFuncCallbacks.find(k);
  if (itr != keyFuncCallbacks.end())
    keyFuncCallbacks.erase(itr);
}

void Templateiser::clearKey ( const char *key )
{
  std::string k;
  tolower(key,k);

  ClassMap::iterator itr = keyClassCallbacks.find(k);
  if (itr != keyClassCallbacks.end())
    keyClassCallbacks.erase(itr);

  KeyMap::iterator itr2 = keyFuncCallbacks.find(k);
  if (itr2 != keyFuncCallbacks.end())
    keyFuncCallbacks.erase(itr2);
}

void Templateiser::flushKeys ( void )
{
  keyClassCallbacks.clear();
  keyFuncCallbacks.clear();
}

void Templateiser::addLoop ( const char *loop, TemplateTestCallback callback )
{
  if (!loop || !callback)
    return;

  std::string l;
  tolower(loop,l);

  loopFuncCallbacks[l] = callback;
  ClassMap::iterator itr = loopClassCallbacks.find(l);
  if (itr != loopClassCallbacks.end())
    loopClassCallbacks.erase(itr);
}

void Templateiser::addLoop ( const char *loop, TemplateCallbackClass *callback )
{
  if (!loop || !callback)
    return;

  std::string l;
  tolower(loop,l);

  loopClassCallbacks[l] = callback;
  TestMap::iterator itr = loopFuncCallbacks.find(l);
  if (itr != loopFuncCallbacks.end())
    loopFuncCallbacks.erase(itr);
}

void Templateiser::clearLoop ( const char *loop )
{
  if (!loop)
    return;

  std::string l;
  tolower(loop,l);

  TestMap::iterator itr = loopFuncCallbacks.find(l);
  if (itr != loopFuncCallbacks.end())
    loopFuncCallbacks.erase(itr);

  ClassMap::iterator itr2 = loopClassCallbacks.find(l);
  if (itr2 != loopClassCallbacks.end())
    loopClassCallbacks.erase(itr2);
}

void Templateiser::flushLoops ( void )
{
  loopClassCallbacks.clear();
  loopFuncCallbacks.clear();
}

void Templateiser::addIF ( const char *name, TemplateTestCallback callback )
{
  if (!name || !callback)
    return;

  std::string n;
  tolower(name,n);

  ifFuncCallbacks[n] = callback;
  ClassMap::iterator itr = ifClassCallbacks.find(n);
  if (itr != ifClassCallbacks.end())
    ifClassCallbacks.erase(itr);
}

void Templateiser::addIF ( const char *name, TemplateCallbackClass *callback )
{
  if (!name || !callback)
    return;

  std::string n;
  tolower(name,n);

  ifClassCallbacks[n] = callback;
  TestMap::iterator itr = ifFuncCallbacks.find(n);
  if (itr != ifFuncCallbacks.end())
    ifFuncCallbacks.erase(itr);
}

void Templateiser::clearIF ( const char *name )
{
  if(!name)
    return;

  std::string n;
  tolower(name,n);

  ClassMap::iterator itr = ifClassCallbacks.find(n);
  if (itr != ifClassCallbacks.end())
    ifClassCallbacks.erase(itr);

  TestMap::iterator itr2 = ifFuncCallbacks.find(n);
  if (itr2 != ifFuncCallbacks.end())
    ifFuncCallbacks.erase(itr2);
}

void Templateiser::flushIFs ( void )
{
  ifClassCallbacks.clear();
  ifFuncCallbacks.clear();
}

bool Templateiser::callKey ( std::string &data, const std::string &key )
{
  std::string lowerKey;
  tolower(key,lowerKey);

  data.clear();

  ClassMap::iterator itr = keyClassCallbacks.find(lowerKey);
  if (itr != keyClassCallbacks.end()) {
    itr->second->keyCallback(data,key);
    return true;
  }

  KeyMap::iterator itr2 = keyFuncCallbacks.find(lowerKey);
  if (itr2 != keyFuncCallbacks.end()) {
    (itr2->second)(data,key);
    return true;
  }

  if (parent)
    return parent->callKey(data,key);

  return false;
}

bool Templateiser::callLoop ( const std::string &key, const std::string &param )
{
  std::string lowerKey;
  tolower(key,lowerKey);

  ClassMap::iterator itr = loopClassCallbacks.find(lowerKey);
  if (itr != loopClassCallbacks.end())
  {
    itr->second->templateParam = param;
   return itr->second->loopCallback(key);
  }

  TestMap::iterator itr2 = loopFuncCallbacks.find(lowerKey);
  if (itr2 != loopFuncCallbacks.end())
    return (itr2->second)(key);

  if (parent)
    return parent->callLoop(key,param);

  return false;
}

bool Templateiser::callIF ( const std::string &key, const std::string &param )
{
  std::string lowerKey;
  tolower(key,lowerKey);

  ClassMap::iterator itr = ifClassCallbacks.find(lowerKey);
  if (itr != ifClassCallbacks.end())
  { 
    itr->second->templateParam = param;
    return itr->second->ifCallback(key);
  }

  TestMap::iterator itr2 = ifFuncCallbacks.find(lowerKey);
  if (itr2 != ifFuncCallbacks.end())
    return (itr2->second)(key);

  if (parent)
    return parent->callIF(key,param);

  return false;
}

bool Templateiser::processTemplateFile ( std::string &code, const char *file )
{
  if (!file)
    return false;

  // find the file
  for (size_t i = 0; i < filePaths.size(); i++ ) {
    std::string path = filePaths[i] + file;
    FILE *fp = fopen(getPathForOS(path).c_str(),"rt");
    std::string val;
   if (fp)
   {
     char c;
     while(fscanf(fp,"%c",&c) == 1)
       val += c;
     fclose(fp);

     processTemplate(code,val);
     return true;
   }
  }
  return false;
}

void Templateiser::processTemplate ( std::string &code, const std::string &templateText )
{
  std::string::const_iterator templateItr = templateText.begin();

  while ( templateItr != templateText.end() ) {
    if ( *templateItr != '[' ) {
      code += *templateItr;
      templateItr++;
    } else {
      templateItr++;

      if (templateItr == templateText.end()) {
	code += '[';
      } else {
	switch(*templateItr) {
	default: // it's not a code, so just let the next loop hit it and output it
	  break;

	case '$':
	  replaceVar(code,++templateItr,templateText);
	  break;

	case '*':
	  processLoop(code,++templateItr,templateText);
	  break;

	case '?':
	  processIF(code,++templateItr,templateText);
	  break;
	case '-':
	case '#': // treat metadata as comments when parsing
	  processComment(code,++templateItr,templateText);
	  break;
	case '!':
	  processInclude(code,++templateItr,templateText);
	  break;
	}
      }
    }
  }
}

void Templateiser::getTemplateMetaData(TemplateMetaData &metadata, const std::string &templateText)
{
  size_t pos = 0;
  while ( pos < templateText.size() && pos != std::string::npos)
  {
    pos = find_first_substr(templateText,std::string("[#"),pos);
    if ( pos < templateText.size() && pos != std::string::npos )
    {
      size_t start = pos;

      pos = find_first_substr(templateText,std::string("]"),pos);
      if ( pos < templateText.size() && pos != std::string::npos )
      {
	std::string dataKey = getStringRange(templateText,start+2,pos-1);

	std::vector<std::string> chunks = tokenize(dataKey,std::string(":"),0,true);
	if (chunks.size() > 1)
	  metadata.add(chunks[0],chunks[1]);
      }
    }
  }
}

bool Templateiser::getTemplateFileMetaData(TemplateMetaData &metadata, const char *file)
{
  if (!file)
    return false;

  // find the file
  for (size_t i = 0; i < filePaths.size(); i++ )
  {
    std::string path = filePaths[i] + file;
    FILE *fp = fopen(getPathForOS(path).c_str(),"rt");
    std::string val;
    if (fp)
    {
      char c;
      while(fscanf(fp,"%c",&c) == 1)
	val += c;
      fclose(fp);

      getTemplateMetaData(metadata,val);
      return true;
    }
  }
  return false;
}


void Templateiser::setPluginName ( const char* name, const char* URL )
{
  if (name)
    pluginName = name;

  if (URL)
    baseURL = URL;
}

void Templateiser::addSearchPath ( const char* path )
{
  if (path)
    filePaths.push_back(std::string(path));
}

void Templateiser::flushSearchPaths ( void )
{
  filePaths.clear();
}

void Templateiser::setDefaultTokens ( void )
{
  addKey("Date",this);
  addKey("Time",this);
  addKey("PageTime",this);
  addKey("BaseURL",this);
  addKey("PluginName",this);
}

void Templateiser::keyCallback ( std::string &data, const std::string &key )
{
  if (key == "date") {
    WeeTime time;
    getLocalTime(time);
    data = format("%d/%d/%d",time.month,time.day,time.year);
  } else if (key == "time") {
    WeeTime time;
    getLocalTime(time);
    data = format("%d:%d:%d",time.hour,time.minute,time.second);
  }else if (key == "pagetime") {
    data = format("%.3f",getTimerSeconds()-startTime);
  } else if (key == "baseurl") {
    data =baseURL;
  } else if (key == "pluginname") {
    data = pluginName;
  }
}

bool Templateiser::loopCallback ( const std::string & /* key */ )
{
  return false;
}

bool Templateiser::ifCallback ( const std::string &key )
{
  return false;
}

// processing helpers

std::string::const_iterator Templateiser::readKey ( std::string &key, std::string::const_iterator inItr, const std::string &str )
{
  std::string::const_iterator itr = inItr;

  while ( itr != str.end() ) {
    if (*itr != ']') {
      key += *itr;
      itr++;
    } else {
      // go past the code
      itr++;
      makelower(key);
      return itr;
    }
  }
  return itr;
}

std::string::const_iterator Templateiser::findNextTag ( const std::vector<std::string> &keys, std::string &endKey, std::string &code, std::string::const_iterator inItr, const std::string &str )
{
  if (!keys.size())
    return inItr;

  std::string::const_iterator itr = inItr;

  while (1) {
    itr = std::find(itr,str.end(),'[');
    if (itr == str.end())
      return itr;

    // save off the itr in case this is the one, so we can copy to this point
    std::string::const_iterator keyStartItr = itr;

    itr++;
    if (itr == str.end())
      return itr;

    std::string key;
    itr = readKey(key,itr,str);

    for ( size_t i = 0; i < keys.size(); i++ ) {
      if ( key == keys[i]) {
	endKey = key;
	code.resize(keyStartItr - inItr);
	std::copy(inItr,keyStartItr,code.begin());
	return itr;
      }
    }
  }
  return itr;
}

void Templateiser::processComment ( std::string & /* code */, std::string::const_iterator &inItr, const std::string &str )
{
  std::string key;
  inItr = readKey(key,inItr,str);
}

void Templateiser::processInclude ( std::string &code, std::string::const_iterator &inItr, const std::string &str )
{
  std::string key;
  inItr = readKey(key,inItr,str);

  // check the search paths for the include file
  if (!processTemplateFile(code,key.c_str()))
    code += "[!" + key + "]";
}

void Templateiser::replaceVar ( std::string &code, std::string::const_iterator &itr, const std::string &str )
{
  // find the end of the ]]
  std::string key;

  itr = readKey(key,itr,str);

  if (itr != str.end()) {
    std::string lowerKey;
    std::string val;

    tolower(key,lowerKey);

    if (callKey(val,lowerKey))
      code += val;
    else
      code += "[$"+key+"]";
  }
}

void Templateiser::processLoop ( std::string &code, std::string::const_iterator &inItr, const std::string &str )
{
  std::string key,loopSection,emptySection,param;

  // read the rest of the key
  std::string::const_iterator itr = readKey(key,inItr,str);

  std::vector<std::string> commandParts = tokenize(key,std::string(" "),0,0);
  if (commandParts.size() < 2)
  {
    inItr = itr;
    return;
  }

  // check the params
  makelower(commandParts[0]);
  makelower(commandParts[1]);

  if (commandParts.size() > 2)
    param = commandParts[2];

  if ( commandParts[0] != "start" )
  {
    inItr = itr;
    return;
  }

  std::vector<std::string> checkKeys;
  checkKeys.push_back(format("*end %s",commandParts[1].c_str()));

  std::string keyFound;
  itr = findNextTag(checkKeys,keyFound,loopSection,itr,str);

  if (itr == str.end())
  {
    inItr = itr;
    return;
  }

  // do the empty section
  // loops have to have both
  checkKeys.clear();
  checkKeys.push_back(format("*empty %s",commandParts[1].c_str()));
  itr = findNextTag(checkKeys,keyFound,emptySection,itr,str);

  if (callLoop(commandParts[1],param))
  {
    std::string newCode;
    processTemplate(newCode,loopSection);
    code += newCode;

    while(callLoop(commandParts[1],param))
    {
      newCode = "";
      processTemplate(newCode,loopSection);
      code += newCode;
    }
  }
  else
  {
    std::string newCode;
    processTemplate(newCode,emptySection);
    code += newCode;
  }
  inItr = itr;
}

void Templateiser::processIF ( std::string &code, std::string::const_iterator &inItr, const std::string &str )
{
  std::string key;

  // read the rest of the key
  std::string::const_iterator itr = readKey(key,inItr,str);

  std::vector<std::string> commandParts = tokenize(key,std::string(" "),0,0);
  if (commandParts.size() < 2) {
    inItr = itr;
    return;
  }

  // check the params
  makelower(commandParts[0]);
  makelower(commandParts[1]);

  if ( commandParts[0] != "if" ) {
    inItr = itr;
    return;
  }

  std::string param;
  if (commandParts.size() > 2)
    param = commandParts[2];

  // now get the code for the next section
  std::string trueSection,elseSection;

  std::vector<std::string> checkKeys;
  checkKeys.push_back(format("?else %s",commandParts[1].c_str()));
  checkKeys.push_back(format("?end %s",commandParts[1].c_str()));

  std::string keyFound;
  itr = findNextTag(checkKeys,keyFound,trueSection,itr,str);

  if (keyFound == checkKeys[0]) { // we hit an else, so we need to check for it
    // it was the else, so go and find the end too
    if (itr == str.end()) {
      inItr = itr;
      return;
    }

    checkKeys.erase(checkKeys.begin());// kill the else, find the end
    itr = findNextTag(checkKeys,keyFound,elseSection,itr,str);
  }

  // test the if, stuff that dosn't exist is false
  if (callIF(commandParts[1],param)) {
    std::string newCode;
    processTemplate(newCode,trueSection);
    code += newCode;
  } else {
    std::string newCode;
    processTemplate(newCode,elseSection);
    code += newCode;
  }
  inItr = itr;
}


// Local Variables: ***
// mode: C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
